﻿// VsPkg.cs : Implementation of NHTrace
//

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using EnvDTE;
using Microsoft.VisualStudio;
using EnvDTE80;
using System.Text;
using System.Threading;
using System.IO;
using System.Reflection;
using Wintellect.PowerCollections;
using System.Linq;
using System.ComponentModel;

namespace NHTrace
{
  /// <summary>
  /// This is the class that implements the package exposed by this assembly.
  ///
  /// The minimum requirement for a class to be considered a valid package for Visual Studio
  /// is to implement the IVsPackage interface and register itself with the shell.
  /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
  /// to do it: it derives from the Package class that provides the implementation of the 
  /// IVsPackage interface and uses the registration attributes defined in the framework to 
  /// register itself and its components with the shell.
  /// </summary>
  // This attribute tells the registration utility (regpkg.exe) that this class needs
  // to be registered as package.
  [PackageRegistration(UseManagedResourcesOnly = true)]
  // A Visual Studio component can be registered under different regitry roots; for instance
  // when you debug your package you want to register it in the experimental hive. This
  // attribute specifies the registry root to use if no one is provided to regpkg.exe with
  // the /root switch.
  [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
  // This attribute is used to register the informations needed to show the this package
  // in the Help/About dialog of Visual Studio.
  [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
  // In order be loaded inside Visual Studio in a machine that has not the VS SDK installed, 
  // package needs to have a valid load key (it can be requested at 
  // http://msdn.microsoft.com/vstudio/extend/). This attributes tells the shell that this 
  // package has a load key embedded in its resources.
  [ProvideLoadKey("Standard", "1.0", "NHTrace", "Jorge Rowies", 104)]
  // This attribute is needed to let the shell know that this package exposes some menus.
  [ProvideMenuResource(1000, 1)]
  // This attribute registers a tool window exposed by this package.
  [ProvideToolWindow(typeof(NHTraceToolWindow))]
  [Guid(GuidList.guidNHTracePkgString)]
  public sealed class NHTracePackage : Package, IVsShellPropertyEvents
  {
    /// <summary>
    /// Default constructor of the package.
    /// Inside this method you can place any initialization code that does not require 
    /// any Visual Studio service because at this point the package object is created but 
    /// not sited yet inside Visual Studio environment. The place to do all the other 
    /// initialization is the Initialize method.
    /// </summary>
    public NHTracePackage()
    {
      //Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
    }

    /// <summary>
    /// This function is called when the user clicks the menu item that shows the 
    /// tool window. See the Initialize method to see how the menu item is associated to 
    /// this function using the OleMenuCommandService service and the MenuCommand class.
    /// </summary>
    private void ShowToolWindow(object sender, EventArgs e)
    {
      // Get the instance number 0 of this tool window. This window is single instance so this instance
      // is actually the only one.
      // The last flag is set to true so that if the tool window does not exists it will be created.
      ToolWindowPane window = this.FindToolWindow(typeof(NHTraceToolWindow), 0, true);
      if ((null == window) || (null == window.Frame))
      {
        throw new NotSupportedException(Resources.CanNotCreateWindow);
      }

      IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
      Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
    }


    /////////////////////////////////////////////////////////////////////////////
    // Overriden Package Implementation
    #region Package Members

    /// <summary>
    /// Initialization of the package; this method is called right after the package is sited, so this is the place
    /// where you can put all the initilaization code that rely on services provided by VisualStudio.
    /// </summary>
    protected override void Initialize()
    {
      //Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
      base.Initialize();

      // Add our command handlers for menu (commands must exist in the .vsct file)
      OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
      if (null != mcs)
      {
        // Create the command for the tool window
        CommandID toolwndCommandID = new CommandID(GuidList.guidNHTraceCmdSet, (int)PkgCmdIDList.cmdNHTrace);
        MenuCommand menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
        mcs.AddCommand(menuToolWin);
      }

      // set an eventlistener for shell property changes
      IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;
      if (shellService != null)
        ErrorHandler.ThrowOnFailure(shellService.AdviseShellPropertyChanges(this, out _cookie));
    }
    #endregion


    #region IVsShellPropertyEvents Members

    private DTE _dte;
    private uint _cookie;

    public int OnShellPropertyChange(int propid, object var)
    {
      // when zombie state changes to false, finish package initialization
      if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
      {
        if ((bool)var == false)
        {
          IESoundDisabler.DisableSounds();

          _dte = GetService(typeof(SDTE)) as DTE;

          // zombie state dependent code

          try
          {
            Window.Package = this;

            string currPath = Assembly.GetExecutingAssembly().CodeBase.Remove(0, "file:///".Length);

            System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(currPath);
            
            string path = config.AppSettings.Settings["templatePath"].Value;
            if (!Path.IsPathRooted(path))
              path = Path.Combine(Path.GetDirectoryName(currPath), path);
            _renderer = new HtmlMessageRenderer(path);

            int logCharSize = int.Parse(config.AppSettings.Settings["logCharSize"].Value);

            new DebugController(
              (DTE2)_dte,
              this,
              new RemotingReceiverProvider(),
              new LoggerProvider(),
              new DebugProcessTracker(),
              logCharSize).Initialize();
          }
          catch (Exception ex)
          {
            Trace.WriteLine(ex.ToString());
            throw;
          }
          
          // eventlistener no longer needed
          IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;
          if (shellService != null)
            ErrorHandler.ThrowOnFailure(shellService.UnadviseShellPropertyChanges(this._cookie));
          _cookie = 0;
        }
      }

      return VSConstants.S_OK;
    }

    #endregion

    private bool _logEnabled = true;
    public bool LogEnabled
    {
      get
      {
        return _logEnabled;
      }
      set
      {
        _logEnabled = value;
      }
    }

    private NHTraceToolWindowControl _window;
    public NHTraceToolWindowControl Window
    {
      get
      {
        if (_window == null)
        {
          ToolWindowPane window = FindToolWindow(typeof(NHTraceToolWindow), 0, true);
          if ((null != window) && (null != window.Frame))
          {
            _window = (NHTraceToolWindowControl)window.Window;  
          }
        }

        return _window;
      }
    }

    private HtmlMessageRenderer _renderer;
    private void renderMessages(StringBuilder messages)
    {
      Window.BeginInvoke((ThreadStart)delegate()
      {
        _renderer.Render(Window.webBrowserEx1, messages);
      });
    }

    private int _messagesSize;
    public int MessagesSize
    {
      get
      {
        return _messagesSize;
      }
    }

    private OrderedMultiDictionary<DateTime, string> _messages = new OrderedMultiDictionary<DateTime, string>(true);

    public int MessageCount
    {
      get 
      {
        return _messages.Count;
      }
    }

    public void AddMessage(DateTime timeStamp, string message)
    {
      _messages.Add(timeStamp, message);
      _messagesSize += message.Length;
    }

    public bool FirstElementHasMessages()
    {
      return _messages.ElementAt(0).Value.Count > 0;
    }

    public void RemoveFirstMessage()
    {
      int sizeToRemove = _messages.ElementAt(0).Value.ElementAt(0).Length;
      _messages.ElementAt(0).Value.Remove(_messages.ElementAt(0).Value.ElementAt(0));
      _messagesSize -= sizeToRemove;
    }

    public void WriteMessagesToWindow()
    {
      StringBuilder messages = new StringBuilder();

      foreach (var item in _messages)
      {
        foreach (var messageStr in item.Value)
          messages.AppendLine(messageStr);
      }

      renderMessages(messages);
    }

    public void ClearMessages()
    {
      _messages.Clear();
      _messagesSize = 0;
    }
  }
}